/**
 * This file is part of aion-emu <aion-emu.com>.
 *
 *  aion-emu is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  aion-emu is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with aion-emu.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aionemu.gameserver.world;

import java.util.Map;
import java.util.concurrent.ScheduledFuture;

import javolution.util.FastList;
import javolution.util.FastMap;

import com.aionemu.commons.utils.SingletonMap;
import com.aionemu.gameserver.ai.CtrlIntention;
import com.aionemu.gameserver.model.gameobjects.Creature;
import com.aionemu.gameserver.model.gameobjects.VisibleObject;
import com.aionemu.gameserver.model.gameobjects.player.Player;
import com.aionemu.gameserver.utils.ThreadPoolManager;

/**
 * Just some part of map.
 * 
 * @author -Nemesiss-
 * 
 */
public class MapRegion
{

	/**
	 * WorldMapInstance witch is parent of this map region.
	 */
	private final WorldMapInstance					parent;

	/**
	 * Surrounding regions + self.
	 */
	private final FastList<MapRegion>				neighbours;

	/**
	 * Objects on this map region.
	 */
	private final FastMap<Integer, VisibleObject>	objects		= new FastMap<Integer, VisibleObject>().shared();
	
	private final Map<Integer, Player>				players		= new SingletonMap<Integer, Player>().shared();

	private volatile boolean active = false;
	private ScheduledFuture<?> neighborsTask;
	/**
	 * Constructor.
	 * 
	 * @param id
	 * @param parent
	 */
	MapRegion(WorldMapInstance parent, boolean is3D)
	{
		if (is3D)
			neighbours = new FastList<MapRegion>(27);
		else
			neighbours = new FastList<MapRegion>(9);
		this.parent = parent;
		this.neighbours.add(this);
	}

	/**
	 * Return World map id.
	 * 
	 * @return world map id
	 */
	public Integer getMapId()
	{
		return getParent().getMapId();
	}

	/** Return an instance of {@link World}, which keeps map, to which belongs this region */
	public World getWorld()
	{
		return getParent().getWorld();
	}

	/**
	 * Returns WorldMapInstance witch is parent of this instance
	 * 
	 * @return parent
	 */
	public WorldMapInstance getParent()
	{
		return parent;
	}

	/**
	 * Returns iterator over AionObjects on this region
	 * 
	 * @return objects iterator
	 */
	public FastMap<Integer, VisibleObject> getVisibleObjects()
	{
		return objects;
	}

	/**
	 * @return the neighbours
	 */
	public FastList<MapRegion> getNeighbours()
	{
		return neighbours;
	}

	/**
	 * Add neighbour region to this region neighbours list.
	 * 
	 * @param neighbour
	 */
	void addNeighbourRegion(MapRegion neighbour)
	{
		neighbours.add(neighbour);
	}

	/**
	 * Add AionObject to this region objects list.
	 * 
	 * @param object
	 */
	void add(VisibleObject object)
	{
		if (objects.put(object.getObjectId(), object) == null)
		{
			if (object instanceof Player)
			{
				players.put(object.getObjectId(), (Player) object);
				// if this is the first player to enter the region, activate self & neighbors
				if (players.size() == 1)
					setActive(true);
			}
		}
	}

	/**
	 * Remove AionObject from region objects list.
	 * 
	 * @param object
	 */
	void remove(VisibleObject object)
	{
		if (objects.remove(object.getObjectId()) != null)
		{
			if (object instanceof Player)
			{
				players.remove(object.getObjectId());
				if (!players.isEmpty())
					startDeactivation();
			}
		}
	}
	
	private final class NeighborsTask implements Runnable
	{
		private final boolean _isActivating;
		
		public NeighborsTask(boolean isActivating)
		{
			_isActivating = isActivating;
		}
		
		@Override
		public void run()
		{
			if (_isActivating)
			{
				for (MapRegion neighbor : getNeighbours())
					neighbor.setActive(true);
			}
			else
			{
				if (areNeighborsEmpty())
					setActive(false);
				
				for (MapRegion neighbor : getNeighbours())
					if (neighbor.areNeighborsEmpty())
						neighbor.setActive(false);
			}
		}
	}
	
	private void setActive(boolean active)
	{
		if (this.active == active)
			return;
		
		this.active = active;
		
		if (!active)
		{
			for (VisibleObject obj : getVisibleObjects().values())
			{
				if (obj instanceof Creature)
				{
					Creature mob = (Creature)obj;
					
					mob.setTarget(null);
					mob.onStopMove();
					mob.getEffectController().removeAllEffects();
					mob.getAggroList().clear();
					mob.getKnownList().clearKnownList();
					if (mob.hasAI())
					{
						mob.getAI().setIntention(CtrlIntention.IDLE);
						mob.getAI().stopAI();
					}
				}
			}
		}
		else
		{
			//TODO ...
		}
	}
	
	public boolean isActive()
	{
		return active;
	}
	
	// check if all 9 neighbors (including self) are inactive or active but with no players.
	// returns true if the above condition is met.
	public boolean areNeighborsEmpty()
	{
		// if this region is occupied, return false.
		if (isActive() && !players.isEmpty())
			return false;
		
		// if any one of the neighbors is occupied, return false
		for (MapRegion neighbor : getNeighbours())
			if (neighbor.isActive() && !neighbor.players.isEmpty())
				return false;
		
		// in all other cases, return true.
		return true;
	}
	
	/**
	 * Immediately sets self as active and starts a timer to set neighbors as active this timer is to avoid turning on
	 * neighbors in the case when a person just teleported into a region and then teleported out immediately...there is
	 * no reason to activate all the neighbors in that case.
	 */
	@SuppressWarnings("unused")
	private void startActivation()
	{
		// first set self to active and do self-tasks...
		setActive(true);
		
		// if the timer to deactivate neighbors is running, cancel it.
		synchronized (this)
		{
			if (neighborsTask != null)
			{
				neighborsTask.cancel(true);
				neighborsTask = null;
			}
			
			// then, set a timer to activate the neighbors
			neighborsTask = ThreadPoolManager.getInstance().schedule(
				new NeighborsTask(true), 1000 );
		}
	}
	
	/**
	 * starts a timer to set neighbors (including self) as inactive this timer is to avoid turning off neighbors in the
	 * case when a person just moved out of a region that he may very soon return to. There is no reason to turn self &
	 * neighbors off in that case.
	 */
	private void startDeactivation()
	{
		// if the timer to activate neighbors is running, cancel it.
		synchronized (this)
		{
			if (neighborsTask != null)
			{
				neighborsTask.cancel(true);
				neighborsTask = null;
			}
			
			// start a timer to "suggest" a deactivate to self and neighbors.
			// suggest means: first check if a neighbor has L2PcInstances in it.  If not, deactivate.
			neighborsTask = ThreadPoolManager.getInstance().schedule(
				new NeighborsTask(false), 1000 * 60);
		}
	}

	/**
	 * @return Returns the players.
	 */
	public Map<Integer, Player> getPlayers()
	{
		return players;
	}
}
